/*
 * UserNet  --  TCP/IP implementation in userspace
 * Copyright (C) 2010  Robert Millan
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <pthread.h>

#include <netinet/in.h>		/* IPPROTO_TCP, IPPROTO_UDP */
#include <netinet/tcp.h>
#include <netinet/udp.h>

#include "unet/ip.h"
#include "unet/ipc.h"
#include "unet/rpc.h"
#include "unet/client.h"

static struct unet_client *global_port_list[2][65536];
static pthread_mutex_t port_list_mutex = PTHREAD_MUTEX_INITIALIZER;

static struct unet_client *
find_port_owner (uint8_t type, uint16_t port)
{
  /* Check if port is used in global list.  */
  if (global_port_list[type][port])
    return global_port_list[type][port];

  /* Check if port is used in any of the local lists.  */
  struct unet_client *owner = NULL;
  auto int hook (struct unet_device *device);
  int hook (struct unet_device *device)
  {
    owner = device->port_list[type * 65536 + port];
    return !!owner;
  }
  unet_device_iterate (hook);

  return owner;
}

int
unet_port (int fd, pid_t pid, uid_t uid, uint8_t type, uint8_t op, struct in_addr listen_addr, uint16_t *num)
{
  int ret = 0;
  struct unet_client **port_list = NULL;

  printf("Processing request from %u\n", pid);

  if (type == UNET_TCP)
    {
      printf("Request is for a TCP port\n");
    }
  else if (type == UNET_UDP)
    {
      printf("Request is for a UDP port\n");
    }
  else
    {
      printf("Invalid request\n");
      return EINVAL;
    }

  pthread_mutex_lock (&port_list_mutex);

  if (listen_addr.s_addr == 0)
    {
      port_list = global_port_list[type];
    }
  else
    {
      if (*num == 0)
	{
	  /* Theoretically possible, but makes no sense.  */
	  ret = EINVAL;
	  goto end;
	}

      auto int hook (struct unet_device *device);
      int hook (struct unet_device *device)
      {
	return (device->ip_addr.s_addr == listen_addr.s_addr);
      }
      port_list = &(unet_device_iterate (hook))->port_list[type * 65536];
      if (! port_list)
	{
	  ret = EADDRNOTAVAIL;
	  goto end;
	}
    }

  if (op == UNET_RPC_PORT_OPEN)
    {
      struct unet_client *owner;

      if (*num == 0)
	{
	  int i;
	  for (i = 1024; i < 65536; i++)
	    if (! find_port_owner (type, i))
	      break;

	  if (i == 65536)
	    {
	      printf("Too many open ports\n");
	      ret = EADDRINUSE;
	      goto end;
	    }

	  printf("Arbitrary port requested, assigning %u\n", i);
	  *num = i;
	}
      else if (find_port_owner (type, *num))
	{
	  printf("Port %u is already open\n", *num);
	  ret = EADDRINUSE;
	  goto end;
	}
      else if (*num < 1024 && uid != 0)
	{
	  printf("Permission denied\n");
	  ret = EPERM;
	  goto end;
	}

      owner = unet_client_find (pid);
      if (! owner)
	{
	  printf("Client %u is not registered\n", pid);
	  ret = EINVAL;
	  goto end;
	}

      printf("Opening port %u\n", *num);
      port_list[*num] = owner;
    }
  else /* op == UNET_RPC_PORT_CLOSE */
    {
      if (*num == 0)
	{
	  printf("Invalid request\n");
	  ret = EINVAL;
	  goto end;
	}

      if (port_list[*num] == NULL)
	{
	  printf("Port %u is already closed\n", *num);
	  ret = EBADF;
	  goto end;
	}

      printf("Closing port %u\n", *num);
      //      port_list[*num] = NULL;
    }

 end:
  pthread_mutex_unlock (&port_list_mutex);

  return ret;
}

void
unet_rpc_ip_incoming_packet (const uint8_t *packet, const uint32_t len)
{
  const struct iphdr *ip_header = (void *) packet;
  const uint8_t *ip_payload = packet + (ip_header->ihl * sizeof (uint32_t));
  struct unet_client *client;

  switch (ip_header->protocol)
    {
    case IPPROTO_TCP:
      {
	const struct tcphdr *tcp_header = (void *) ip_payload;
	client = find_port_owner (UNET_TCP, ntohs (tcp_header->dest));
	break;
      }
    case IPPROTO_UDP:
      {
	const struct udphdr *udp_header = (void *) ip_payload;
	client = find_port_owner (UNET_UDP, ntohs (udp_header->dest));
	break;
      }
    default:
      /* This one's not interesting... */
      return;
    }

  if (client)
    {
      printf("sending packet with protocol %u to client %u\n",
		    ip_header->protocol, client->pid);

      /* Send the whole IP packet to client (without decapsulating).  */
      send (client->fd, packet, len, MSG_DONTWAIT);
    }
}
